package xm.components.spinner
{
	import com.greensock.TweenLite;
	import com.greensock.data.TweenLiteVars;
	import com.greensock.easing.Circ;
	import com.greensock.easing.Cubic;
	
	import flash.display.Graphics;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	import xm.core.XMSprite;
	import xm.events.SelectionEvent;
	import xm.events.SpinnerEvent;
	import xm.manager.ConfigManager;
	import xm.manager.StageManager;
	
	
	/**
	 * selectionChange : SelectionEvent
	 */	
	[Event(name="selectionChange", type="xm.events.SelectionEvent")]
	
	/**
	 * spinnerComplete : SpinnerEvent
	 */	
	[Event(name="spinnerComplete", type="xm.events.SpinnerEvent")]
	
	/**
	 * spinnerUpdate : SpinnerEvent
	 */	
	[Event(name="spinnerUpdate", type="xm.events.SpinnerEvent")]
	
	/**
	 * <br> 클래스 설명 : </br>
	 * @author 김정환
	 */	
	public class Spinner extends XMSprite
	{
		
		//-----------------------------------------------------------------------------------
		// public vars
		//-----------------------------------------------------------------------------------
		
		/**
		 *  Spin item 사이의 gap
		 */		
		public var gapBetweenItem : Number = 0;
		
		/**
		 * itemHeight - Spin item 의 기본height
		 */		
		public var itemHeight : Number = 40;
		
		/**
		 * 스크롤 센스를 위한 임시변수 ( 외부조작하지 말것 ) 
		 */		
		public var __currentPower__ : Number = 0;
		
		/**
		 * 가상의 스크롤 후의 포지션. 
		 */		
		public var scrolledPosY : Number = 0;
		
		/**
		 * 스핀이 움직이고 있는지 체크.
		 */		
		public var isSpinMove : Boolean = false;
		
		//-----------------------------------------------------------------------------------
		// protected / internal vars
		//-----------------------------------------------------------------------------------
		
		/**
		 * dataProvider 에 의해 삽입된 dataList 
		 */		
		protected var data : Array = [];
		
		/**
		 * renderer에 의해 생성되어 현재 작동하는 List
		 */		
		protected var itemList : Array = [];
		
		/**
		 * renderer에 의해 생성되었지만 현재 작동하지 않는 garbageList 
		 */		
		protected var itemGarbageList : Array = [];
		
		/**
		 * Rendering 을 실제 할 클래스타입. 
		 */		
		protected var RenderClass : Class = SpinnerListItemRenderer;
		
		
		//-----------------------------------------------------------------------------------
		// private vars
		//-----------------------------------------------------------------------------------
		
		// skin
		private var _drumSkin : MovieClip;
		private var _maskClip : Shape;
		
		// scroll relative
		private var _scrollPT : Point;
		private var _addPower : Number = 0;
		private var _limitY : Number = 0;
		
		private var _minusPowerRate : Number = 5; // 스크롤 영역 넘어선상태에서의 반작용 비율.
		private var _pickerTotalHeight : Number = 0;
		
		// cache
		private var _selectedData : Object;
		
		// 보더의 두깨( 연한회색 )
		private var _defaultBorderThickness : Number = 6;
		
		private var _isInitialize : Boolean = false;
		private var _proxyInitIndex : int = -1;
		
		//-----------------------------------------------------------------------------------
		// public functions
		//-----------------------------------------------------------------------------------
		
		/**
		 * constructor 
		 */	
		public function Spinner()
		{
			super();
		}
		
		
		
		/**
		 * commitProperties
		 */		
		override public function commitProperties():void
		{
			super.commitProperties();
			
		}
		
		
		
		/**
		 * updateDisplayList
		 */		
		override public function updateDisplayList( enforce : Boolean = false ):void
		{
			super.updateDisplayList(enforce);
			
			var len : int = data.length,
				item : SpinnerListItemRenderer,
				i : int = 0,
				availWidth : Number = viewWidth-_defaultBorderThickness,
				availHeight : Number = viewHeight;
			
			
			// 아직 초기화가 안되어있는데, 총 높이와, 
			// viewHeight 이 셋팅되어있다면 scrolledPosY 를 초기값으로 셋팅한다.
			if( _isInitialize == false && _pickerTotalHeight && viewHeight )
			{
				_isInitialize = true;
				
				
				// 최초 생성 및 위치잡기.
				scrolledPosY = 0;
				updateDisplayList( true );
				
				// 포지션 갱신.
				// selectedIndex 셋팅 내역이 있으면 index셋팅하고,  
				// 없으면 ( 0번 item이 화면 중앙에 오도록 )
				if( _proxyInitIndex != -1 )
				{
					_selectedData = data[ _proxyInitIndex ];
					scrolledPosY = viewHeight/2-itemHeight/2-_proxyInitIndex*itemHeight;
				}
				else
				{
					_selectedData = data[ 0 ];
					scrolledPosY = viewHeight/2-itemHeight/2;
				}
				
				_proxyInitIndex = -1;
			}
			
			
			_drumSkin.width = viewWidth;
			_drumSkin.height = viewHeight;
			
			_maskClip.width = viewWidth;
			_maskClip.height = viewHeight;
			
			//----------- 스크롤 범위보다 큰경우
			if( scrolledPosY > 0 ) 
			{
				len = itemList.length;
				while( i<len )
				{
					item = itemList[ i ] as SpinnerListItemRenderer;
					item.viewHeight = itemHeight;
					item.viewWidth = availWidth;
					
					item.dataProvider = data[ i ]; 
					item.y = (itemHeight+gapBetweenItem)*i + scrolledPosY;
					item.x = ( viewWidth-item.viewWidth )/2;
					item.backgroundAlpha = 0;
					
					item.updateDisplayList( true );
					++i;
				}
			}
			else //-------------- 스크롤범위보다 작거나 정상적인 스크롤
			{
				// 1. 전체 높이를 셋팅하고,
				//				this.viewHeight = len*(itemHeight+gapBetweenItem);
				
				// 2. 리스트를 전부 비운 후.
				trashList();
				
				// 3. 스크롤 된만큼 ( 아이템의 높이+갭 )을 가지고 나눈 몫을 시작점으로 설정한다. 
				i = Math.floor( Math.abs( scrolledPosY )/(itemHeight+gapBetweenItem) );
				
				// 4. while 문의 length 는 시작점~ data 의 길이와, 화면의 총 리스트갯수중 적은것으로 선택한다.
				len = Math.min( len, Math.ceil((availHeight)/(itemHeight+gapBetweenItem) )+i+1 );
				
				// 5. 렌더링한다.
				while( i<len )
				{
					if( i > itemList.length - 1 )
					{
						item = getItem();
					}
					else
					{
						item = itemList[ i ] as SpinnerListItemRenderer;
					}
					
					item.viewHeight = itemHeight;
					item.viewWidth = availWidth;
					
					item.dataProvider = data[ i ];
					item.y = (itemHeight+gapBetweenItem)*i + scrolledPosY;
					item.x = ( viewWidth-item.viewWidth )/2;
					item.backgroundAlpha = 0;
					
					
					item.updateDisplayList( true );
					itemList.push( item );
					addChildAt( item, 3 );
					
					++i;
				}
			}
		}
		
		
		
		
		
		
		//-----------------------------------------------------------------------------------
		// life cycle functions
		//-----------------------------------------------------------------------------------
		
		/**
		 * createChildren
		 */		
		override public function createChildren():void
		{
			super.createChildren();
			
			
			this.backgroundColor = 0x000000;
			this.backgroundAlpha = 1;
			
			_maskClip = new Shape();
			var g : Graphics = _maskClip.graphics;
			g.beginFill(0x000000, 1 );
			g.drawRect(0,0,1,1);
			g.endFill();
			addChild( _maskClip );
			
			
			_drumSkin = new XM_DEFAULT_SKIN_PICKER_DRUM_SKIN();
			addChild( _drumSkin );
			
			this.mask = _maskClip;
			
			this.addEventListener(MouseEvent.MOUSE_DOWN, onDown );
			
		}
		
		/**
		 * createComplete
		 */		
		override public function creationComplete():void
		{
			super.creationComplete();
			
		}
		
		
		/**
		 * add
		 */		
		override public function add():void
		{
			super.add();
		}
		
		
		/**
		 * remove
		 */		
		override public function remove():void
		{
			super.remove();
		}
		
		
		/**
		 * destroy
		 */		
		override public function destroy():void
		{
			TweenLite.killTweensOf( this );
			
			var s : Stage, i:int, len : int;
			
			s = StageManager.getInstance().stage;
			
			
			// 이벤트제거.
			s.removeEventListener(MouseEvent.MOUSE_MOVE, onMove );
			s.removeEventListener(MouseEvent.MOUSE_UP, onUp );
			s.removeEventListener(Event.MOUSE_LEAVE, onUp );
			this.removeEventListener( Event.ENTER_FRAME, onEnter );
			this.removeEventListener(MouseEvent.MOUSE_DOWN, onDown );
			
			
			// public 속성제거.
			gapBetweenItem = NaN;
			itemHeight = NaN;
			__currentPower__ = NaN;
			scrolledPosY = NaN;
			isSpinMove = false;
			
			
			
			// 리스트 제거
			if( data )
			{
				i=0;
				len=data.length;
				while( i<len )
				{
					delete data[ i ];
					++i;
				}
				data = null;
			}
			
			
			i=0;
			len=itemList.length;
			while( i<len )
			{
				( itemList[ i ] as SpinnerListItemRenderer ).destroy();
				delete itemList[ i ];
				++i;
			}
			itemList = null;
			
			
			i=0;
			len=itemGarbageList.length;
			while( i<len )
			{
				( itemGarbageList[ i ] as SpinnerListItemRenderer ).destroy();
				delete itemGarbageList[ i ];
				++i;
			}
			itemGarbageList = null;
			
			RenderClass = null;
			
			
			// 자식제거.
			if( _drumSkin )
			{
				if( _drumSkin.parent ) _drumSkin.parent.removeChild( _drumSkin );
				_drumSkin = null;
			}
			
			if( _maskClip )
			{
				if( _maskClip.parent ) _maskClip.parent.removeChild( _maskClip );
				_maskClip = null;
			}
			
			_scrollPT = null;
			_addPower = NaN;
			_limitY = NaN;
			_minusPowerRate = NaN;
			_pickerTotalHeight = NaN;
			_selectedData = null;
			_defaultBorderThickness = NaN;
			_isInitialize = false;
			_proxyInitIndex = 0;
			
			
			
			super.destroy();
		}
		
		
		/**
		 * 데이터와 매칭되는 인덱스 반환.
		 * <br />
		 * @param data
		 * @return 
		 */		
		public function getIndexAtData( data : Object ):int
		{
			var i:int = 0, len:int = dataProvider.length;
			
			while( i<len )
			{
				if( dataProvider[ i ] === data )
				{
					return i;
				}
			}
			
			return -1;
		}
		
		//-----------------------------------------------------------------------------------
		// setter / getter
		//-----------------------------------------------------------------------------------
		
		/**
		 * <br> Picker 에서 사용될 List Data </br>
		 * @param list
		 */
		public function set dataProvider( list : Array ):void
		{
			data = list;
			_pickerTotalHeight = data.length*(itemHeight+gapBetweenItem);
		}
		
		/**
		 * @return 
		 */		
		public function get dataProvider():Array
		{
			return data;
		}
		
		
		/**
		 * @param IR : Class extends ViewListItemRenderer 
		 */		
		public function set itemRenderer( IR : Class ):void
		{
			RenderClass = IR;
		}
		
		
		/**
		 * 선택 인덱스 변경.
		 * @param index
		 */		
		public function set selectedIndex( index:int ):void
		{
			if( _isInitialize == false )
			{
				_proxyInitIndex = index;
			}
			else
			{
				_selectedData = data[ index ];
				TweenLite.to( this, 1, {scrolledPosY:viewHeight/2-itemHeight/2-index*itemHeight, ease:Cubic.easeOut, onUpdate:updateAndDispatchEvent, onComplete:dispatchSelectionChangeEvent} );
			}
		}
		
		/**
		 * 선택 인덱스 반환
		 * @return int - selectedIndex
		 */
		public function get selectedIndex():int
		{
			return data.indexOf( selectedData );
		}
		
		/**
		 * 선택 데이터 변경으로 인덱스 셋팅을 다시한다.
		 * @param data
		 */
		public function set selectedData( data : Object ):void
		{
			selectedIndex = dataProvider.indexOf( data );
		}
		
		/**
		 * 선택된 데이터 반환.
		 */		
		public function get selectedData():Object
		{
			return _selectedData;
		}
		//-----------------------------------------------------------------------------------
		// protected functions
		//-----------------------------------------------------------------------------------
		
		
		
		//-----------------------------------------------------------------------------------
		// internal functions
		//-----------------------------------------------------------------------------------
		
		
		
		
		//-----------------------------------------------------------------------------------
		// private functions
		//-----------------------------------------------------------------------------------
		
		
		private function updateAndDispatchEvent():void
		{
			this.invalidateDisplayList();
			dispatchSpinnerUpdateEvent();
		}
		
		
		// 리스트 비우기
		private function trashList():void
		{
			var i:int = -1,
				len : int = itemList.length,
				item : SpinnerListItemRenderer;
			
			while( ++i<len )
			{
				item = itemList.pop() as SpinnerListItemRenderer;
				if( item && item.parent ) item.parent.removeChild( item );
				itemGarbageList.push( item );
			}
		}
		
		
		
		// 리스트 아이템 재활용.
		private function getItem():SpinnerListItemRenderer
		{
			if( RenderClass == null )
			{
				throw new Error( "RenderClass 가 정의되지 않았습니다. itemRenderer 를 등록해주세요." );
				return;
			}
			
			var item : SpinnerListItemRenderer;
			if( itemGarbageList.length > 0 )
			{
				item = itemGarbageList.pop() as SpinnerListItemRenderer;
			}
			else
			{
				item = new RenderClass();
			}
			
			return item as SpinnerListItemRenderer;
		}		
		
		
		
		// 스크롤 영역인지 체크.
		private function checkOutOfBound():Boolean
		{
			// 스크롤 영역을 벗어남.
			// 스크롤 포지션이 너무 아래로 쳐졌거나, 너무 위로 갔을경우 원복, 
			if( scrolledPosY > viewHeight/2-itemHeight/2 || scrolledPosY < _limitY )
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		
		
		
		// 트윈 업데이트중.
		private function onTweenUpdate():void
		{
			if( checkOutOfBound() )
			{
				onTweenInitReadyToBack();
			}
			
			// 쩜좌표 제거.
			scrolledPosY = Math.round( scrolledPosY );
			
			updateAndDispatchEvent();
		}
		
		
		// 트윈 완료시. mark에 가까운 녀석으로 셋팅.
		private function onTweenComplete():void
		{
			var i:int, len:int, 
				item:SpinnerListItemRenderer, 
				centerY : Number,
				centerLimitY : Number,
				closeToCenterItem : SpinnerListItemRenderer,
				compareCenterNum : Number;
			
			closeToCenterItem = itemList[ 0 ] as SpinnerListItemRenderer;
			centerY = viewHeight/2-itemHeight/2;
			
			i=1;
			len=itemList.length;
			
			compareCenterNum = centerY - itemHeight/2;
			centerLimitY = compareCenterNum + itemHeight;
			
			while( i<len )
			{
				item = itemList[ i ] as SpinnerListItemRenderer;
				
				if( item.y > compareCenterNum && item.y < centerLimitY )
				{
					if( Math.abs( item.y-centerY ) < Math.abs( closeToCenterItem.y - centerY ) )
					{
						closeToCenterItem = item;
					}
				}
				
				++i;
			}
			
			// 선택 데이터를 변경하고, 이벤트를 발생한다.
			_selectedData = closeToCenterItem.dataProvider;
			var selectedInx : int = selectedIndex;
			
			
			if( closeToCenterItem )
			{
				TweenLite.to( this, 0.3, {scrolledPosY:scrolledPosY+centerY-closeToCenterItem.y, ease:Cubic.easeOut, onUpdate:updateAndDispatchEvent, onComplete:dispatchSelectionChangeEvent} );
			}
			else
			{
				dispatchSelectionChangeEvent();
			}
		}		
		
		// 1. 스크롤 트윈을 원상복구 시키기 전 잔여힘에 의한효과.
		private function onTweenInitReadyToBack():void
		{
			TweenLite.killTweensOf( this );
			TweenLite.to( this, Math.min( 0.1, __currentPower__/30 ), {scrolledPosY:scrolledPosY+__currentPower__, ease:Cubic.easeOut, onUpdate:updateAndDispatchEvent, onComplete:onTweenInitPos} );
			__currentPower__ = 0;
		}
		
		// 2. 스크롤 트윈을 원상복구 시킴.
		private function onTweenInitPos():void
		{
			if( scrolledPosY > viewHeight/2-itemHeight/2 || (this._pickerTotalHeight+viewHeight) < viewHeight )
			{
				TweenLite.killTweensOf( this );
				_selectedData = data[ 0 ];
				TweenLite.to( this, 0.3, {scrolledPosY:viewHeight/2-itemHeight/2, ease:Cubic.easeOut, onUpdate:updateAndDispatchEvent, onComplete:dispatchSelectionChangeEvent} );
			}
			else if( scrolledPosY < _limitY )
			{
				TweenLite.killTweensOf( this );
				_selectedData = data[ data.length-1 ];
				TweenLite.to( this, 0.3, {scrolledPosY:_limitY, ease:Cubic.easeOut, onUpdate:updateAndDispatchEvent, onComplete:dispatchSelectionChangeEvent} );
			}
			else
			{
				onTweenComplete();
			}
		}
		
		
		
		
		// 선택영역 변경 이벤트.
		// 움직임 완료 이벤트도 함께 발생한다.
		private function dispatchSelectionChangeEvent():void
		{
			this.isSpinMove = false;
			
			var selectionEvent : SelectionEvent = new SelectionEvent( SelectionEvent.SELECTION_CHANGE, false, false );
			selectionEvent.data = selectedData;
			
			var spinnerEvent : SpinnerEvent = new SpinnerEvent( SpinnerEvent.SPINNER_COMPLETE, false, false );
			spinnerEvent.currentData = selectedData;
			spinnerEvent.currentIndex = selectedIndex;
			
			dispatchEvent( selectionEvent );
			dispatchEvent( spinnerEvent );
		}
		
		
		// 스핀 업데이트 관련 이벤트.
		private function dispatchSpinnerUpdateEvent():void
		{
			this.isSpinMove = true;
			
			var spinnerEvent : SpinnerEvent = new SpinnerEvent( SpinnerEvent.SPINNER_UPDATE, false, false );
			spinnerEvent.currentData = selectedData;
			spinnerEvent.currentIndex = selectedIndex;
			
			dispatchEvent( spinnerEvent );
		}
		
		
		
		//-----------------------------------------------------------------------------------
		// event handler
		//-----------------------------------------------------------------------------------
		/**
		 * 
		 * @param e
		 */		
		protected function onDown(e:MouseEvent):void
		{
			
			e.stopPropagation();
			
			var s : Stage;
			
			s = StageManager.getInstance().stage;
			
			s.addEventListener(MouseEvent.MOUSE_MOVE, onMove );
			s.addEventListener(MouseEvent.MOUSE_UP, onUp );
			s.addEventListener(Event.MOUSE_LEAVE, onUp );
			
			_addPower = 0;
			_scrollPT = new Point( s.mouseX, s.mouseY );
		}		
		
		/**
		 * @param e
		 */		
		protected function onMove(e:MouseEvent):void
		{
			e.stopPropagation();
			
			// 5만큼은 움직여야지 무브로 간주한다.
			if( Math.abs( StageManager.getInstance().stage.mouseY-_scrollPT.y ) > ConfigManager.FLICK_SENSE_NUM ) 
			{
				// 일단 무브가 시작되면 클릭은 발생하지 않음.
				this.addEventListener( Event.ENTER_FRAME, onEnter );
			}
		}
		
		/**
		 * 
		 * @param e
		 */		
		protected function onEnter(e:Event):void
		{
			var s : Stage = StageManager.getInstance().stage,
				currentValue : Number = s.mouseY-_scrollPT.y;
			
			_limitY = -(_pickerTotalHeight-viewHeight/2)+itemHeight/2;
			
			// 최후의 이동역역을 가지고 파워를 비율로 증가시키는 로직 ( 추천 )
			_addPower = ( s.mouseY-_scrollPT.y );
			_addPower = ( _addPower > 0 ) ? Math.floor( _addPower ) : Math.round( _addPower );
			
			// 드래그하는동안 파워를 감소시킨다.
			__currentPower__ = __currentPower__-__currentPower__/_minusPowerRate;
			
			if( currentValue == 0 ) return;
			
			if( checkOutOfBound() )
			{
				scrolledPosY += Math.floor( currentValue/3 ); 
			}
			else
			{
				scrolledPosY += Math.floor( currentValue ); 
			}
			
			
			_scrollPT = new Point( s.mouseX, s.mouseY );
			
			// 쩜좌표 제거.
			scrolledPosY = Math.round( scrolledPosY );
			
			this.updateAndDispatchEvent();
		}
		
		/**
		 * 
		 * @param e
		 */		
		protected function onUp(e:MouseEvent):void
		{
			e.stopPropagation();
			
			var s : Stage = StageManager.getInstance().stage,
				targetY : Number,
				tweenVars : TweenLiteVars;
			
			this.removeEventListener( Event.ENTER_FRAME, onEnter );
			s.removeEventListener(MouseEvent.MOUSE_MOVE, onMove );
			s.removeEventListener(MouseEvent.MOUSE_UP, onUp );
			s.removeEventListener(Event.MOUSE_LEAVE, onUp );
			
			__currentPower__ +=_addPower;
			
			targetY = scrolledPosY+__currentPower__*10;
			
			tweenVars = new TweenLiteVars();
			tweenVars.prop( "scrolledPosY", targetY );
			tweenVars.prop( "__currentPower__", 0 );
			tweenVars.onUpdate( onTweenUpdate );
			tweenVars.onComplete( onTweenComplete );
			tweenVars.ease( Circ.easeOut );
			
			
			// 화면 사용가능높이보다, 컨텐츠가 길경우에만 영역벗어남을 체크한다.
			if( this._pickerTotalHeight > viewHeight && checkOutOfBound() )
			{
				// 영역벗어나면 원위치로 하는 탄성적용
				onTweenInitReadyToBack();
			}
				// 그게아닐경우 스크롤한다
			else if( this._pickerTotalHeight <= viewHeight )
			{
				TweenLite.to( this, 1, tweenVars ); // duration 1초로 고정.
			}
			else if( Math.abs( __currentPower__ ) > 0.1 )
			{
				TweenLite.to( this, 1, tweenVars ); // duration 1초로 고정.
			}
			else
			{
				onTweenComplete();
			}
		}
		
		
		
	}
}





